import { compileToFunction } from './compiler/index.js';
import { defineReactive, observe } from './observer/index.js';
import Watcher from './observer/watcher.js';

function _proxy(data) {
  // 循环data中所有的键，统统代理到vm实例上
  for (const key of Object.keys(data)) {
    Object.defineProperty(this, key, {
      get() {
        return data[key];
      },
      set(newValue) {
        data[key] = newValue;
      }
    });
  }
}

function initData(vm) {
  // 观察_data中所有的键值对的存取值操作
  observe(vm._data);
  // 将数据代理到实例身上，可以通过实例直接访问数据
  _proxy.call(vm, vm._data);
}

function initWatch(vm, watch) {
  if (watch) {
    const entries = Object.entries(watch);
    for (const [key, value] of entries) {
      // 实现watch，并执行侦听器的回调
      // new Watcher => constructor => this.get() => Dep.target = this
      // => 访问this.vm._data[this.exp] => getter => dep.depend() =>
      // this.subs.push(Dep.target) => Dep.target = null
      new Watcher(vm, key, value);
    }
  }

  Vue.prototype.$watch = function (exp, cb) {
    // 实现$watch方法，并执行侦听器的回调
    new Watcher(this, exp, cb);
  };
}

function initComputed(vm, computed) {
  if (computed) {
    for (const [name, fn] of Object.entries(computed)) {
      const watcher = new Watcher(vm, fn, undefined, {
        lazy: true
      });

      // 计算属性代理到实例上
      Object.defineProperty(vm, name, {
        configurable: true,
        enumerable: true,
        get() {
          // 如果计算属性的watcher变脏了，才需要重新计算
          if (watcher.dirty) {
            // 有可能数据已经发生变化了，
            // 计算属性就要计算出新的值
            watcher.value = watcher.get();
            // 如果计算过一次，就要标记为干净的
            watcher.dirty = false;
          }

          // 否则，就直接返回上一次的计算结果
          return watcher.value;
        },
        set() {
          console.log('简写形式的computed不能赋值');
        }
      });
    }
  }
}

function Vue(options) {
  const vm = this;

  vm.$options = options;
  // 如果data是个函数，那么就需要调用取到其返回值再赋值，不是函数，便可直接赋值
  vm._data = typeof options.data === 'function' ? options.data() : options.data;
  initData(vm);

  // 计算属性的初始化动作应该是在data之后，watch之前
  initComputed(vm, options.computed);

  initWatch(vm, options.watch);

  if (options.el) {
    this.$mount(options.el);
  }
}

Vue.prototype.$mount = function (el) {
  const vm = this;
  vm.$el = document.querySelector(el);

  let render = vm.$options.render;
  if (!render) {
    let template = vm.$options.template;
    if (!template) {
      template = vm.$el.outerHTML;
    }
    const render = compileToFunction(template);
  }
};

// 因为this.b.b3 = 4是没有响应式的
// this.b = {}
// vm.$set(vm.b, 'b3', 4)
// 其实b这个对象是变了的，就要通知这个对象所有的Watcher

Vue.prototype.$set = function (obj, key, value) {
  // keys不会拿原型链上的键
  const hasKey = Object.keys(obj).includes(key);

  // 添加这个属性，并做响应式处理
  defineReactive(obj, key, value);

  // 会判断到原型链上去
  // if (key in obj) {}
  if (!hasKey) {
    // obj变了，要通知所有的Watcher
    obj.__ob__.dep.notify();
  }
};

export default Vue;
